﻿using System;
using System.Collections.Generic;
using System.Data.Linq;
using System.Linq;
using System.Linq.Expressions;
using CoreLibrary;
using CoreLibrary.Paging;
using CoreLibrary.Repo;
using CoreLibrary.Sql.Ext;
using Entities.DataBase.Obj;
using Entities.DataBase.Persistent;
using Entities.DataBase.Sql;

namespace Entities.DataBase.Impl {

    public class OrderRepository : BaseRepository<Order>, IOrderRepository {

        private static readonly Func<DataContext, int, Order>
           LinqGetOrder = CompiledQuery.Compile((DataContext context, int id) => 
               context.GetTable<Order>().SingleOrDefault(o => o.OrderId == id));

        private static readonly Func<DataContext, IQueryable<Order>>
           LinqGetAllOrders = CompiledQuery.Compile((DataContext context) => 
                context.GetTable<Order>().OrderByDescending(o => o.DateCreated));

        private static readonly Func<DataContext, IQueryable<OrderRecord>>
           LinqGetAllOrderRecords = CompiledQuery.Compile((DataContext context) =>
               context.GetTable<OrderRecord>().OrderByDescending(o => o.DateCreated));

        private static readonly Func<DataContext, string, IQueryable<OrderRecord>>
           LinqGetRecordsByOrderGuid = CompiledQuery.Compile((DataContext context, string orderId) =>
               context.GetTable<OrderRecord>().Where(o => o.OrderId == orderId));

        private static readonly Func<DataContext, IQueryable<OrderInfo>>
            LinqListAllOrderInfos = CompiledQuery.Compile((DataContext context)
              => (
                  from order in context.GetTable<Order>().OrderByDescending(o => o.DateCreated)
                  join customer in context.GetTable<Customer>()
                  on order.CustomerId equals customer.Id
                  select new OrderInfo {
                      Id            = order.OrderId,
                      OperatorId    = order.OperatorId,
                      CustomerId    = customer.Id,
                      CustomerName  = customer.CName,
                      Status        = order.Status,
                      DateCreated   = order.DateCreated,
                      DateUpdated   = order.DateUpdated,
                      Description   = order.Description,
                      Price         = order.Price,
                      Profit        = order.Profit,
                      Cost          = order.Cost,
                      Postage       = order.OrderPostage,
                      TotalPostage  = order.TotalPostage,
                      CourierId     = order.CourierId,
                      TrackingId    = order.TrackingId,
                  }
            )
        );

        private static readonly Func<DataContext, int, int, IQueryable<OrderInfo>>
            LinqListOrderInfos = CompiledQuery.Compile((DataContext context, int offset, int limit)
              => (
                  from order in context.GetTable<Order>().OrderByDescending(o => o.DateCreated).Skip(offset).Take(limit)
                  join customer in context.GetTable<Customer>()
                  on   order.CustomerId equals customer.Id
                  select new OrderInfo {
                      Id           = order.OrderId,
                      OperatorId   = order.OperatorId,
                      CustomerId   = customer.Id,
                      CustomerName = customer.CName,
                      Status       = order.Status,
                      DateCreated  = order.DateCreated,
                      DateUpdated  = order.DateUpdated,
                      Description  = order.Description,
                      Price        = order.Price,
                      Profit       = order.Profit,
                      Cost         = order.Cost,
                      Postage      = order.OrderPostage,
                      TotalPostage = order.TotalPostage,
                      CourierId    = order.CourierId,
                      TrackingId   = order.TrackingId,
                  }
            )
        );

        private static readonly Func<DataContext, string, IQueryable<Order>>
          LinqGetOrdersByCustomerId = CompiledQuery.Compile((DataContext context, string customerId) =>
               context.GetTable<Order>().Where(o => o.CustomerId.Equals(customerId)).OrderByDescending(o => o.DateCreated));

        public override string TableName {
            get {
                return "D_ORDER";
            }
        }

        public Order Get(int orderId) {
            return Execute(context => LinqGetOrder(context, orderId));
        }

        public PagingResult<Order> List(PagingRequest request) {
            return Execute(context => new PagingResult<Order> {
                Offset = request.Offset,
                Data   = LinqGetAllOrders(context)
                        .Skip(request.Offset)
                        .Take(request.Limit)
                        .ToList()
            });
        }

        public List<OrderInfo> ListAll() {
            return Execute(context => LinqListAllOrderInfos(context).ToList());
        }

        public List<OrderInfo> ListMany(int offset, int limit) {
             return Execute(context => LinqListOrderInfos(context, offset, limit).ToList());
        }

        public List<Order> ListByCustomerId(string customer) {
            return Execute(context => LinqGetOrdersByCustomerId(context, customer).ToList());
        }

        public List<OrderInfo> SearchOrders(SearchOrderReq request) {
            var exps = new List<Expression<Func<OrderInfo, bool>>> {
                t => request.CourierId == null || t.CourierId == request.CourierId,
                t => request.Status == null    || t.Status == request.Status,
                t => t.CustomerName.Contains(request.CustomerName) || string.Empty.Equals(request.CustomerName),
                t => t.DateCreated.Date.CompareTo(request.DateFrom.Date) >= 0 && t.DateCreated.Date.CompareTo(request.DateTo.Date) <= 0
            };
            return Execute(context => LinqListAllOrderInfos(context).Where(PredicateUtil.And(exps)).ToList());
        }

        public void Create(Order order, List<OrderRecord> records) {
            var productStocks = Execute(new ListStocksByIds("D_PRODUCT_STOCK", records.Select(r => r.StockId).ToList()))
                               .ToDictionary(s => s.Id);
            Execute((context,ts) => {
                foreach (var record in records) {
                    var stock = productStocks[record.StockId];
                    if (stock == null) {
                        ts.Complete();
                        throw new Exception("产品库存不存在");
                    }

                    if (stock.Count == 0) {
                        ts.Complete();
                        throw new Exception("产品库存为零");
                    }

                    stock.DateUpdated = DateTime.UtcNow;
                    stock.Count = Math.Max(stock.Count - record.Count, 0);
                }
                context.GetTable<Order>().InsertOnSubmit(order);
                context.GetTable<OrderRecord>().InsertAllOnSubmit(records);
                context.SubmitChanges();
                ts.Complete();
            });
        }

        public void Remove(int orderId) {
            Execute((context, ts) => {
                var orderInfo    = LinqGetOrder(context, orderId);
                var orderRecords = LinqGetRecordsByOrderGuid(context, orderInfo.Guid).ToList();

                var productStockIds = orderRecords.Select(r => r.StockId).ToList();
                var productStocks   = context.GetTable<Stock>().Where(s => productStockIds.Contains(s.Id));

                foreach (var record in orderRecords) {
                    var stock = productStocks.SingleOrDefault(s => s.Id == record.StockId);
                    if (stock == null) {
                        continue;
                    }

                    var count         = stock.Count;
                    stock.Count       = stock.Count + record.Count;
                    stock.Price       = (count * stock.Price   + record.Count * record.Cost)    / (count + record.Count);
                    stock.Postage     = (count * stock.Postage + record.Count * record.Postage) / (count + record.Count);
                    stock.DateUpdated = DateTime.UtcNow;
                }

                context.GetTable<Order>().DeleteOnSubmit(orderInfo);
                context.GetTable<OrderRecord>().DeleteAllOnSubmit(orderRecords);

                context.SubmitChanges();
                ts.Complete();
            });
        }

        public void RemoveByGuid(string guid) {
            Execute((context, ts) => {
                var order   = context.GetTable<Order>().SingleOrDefault(o => o.Guid.Equals(guid));
                var records = LinqGetRecordsByOrderGuid(context, guid).ToList();
                if (order != null) {
                    context.GetTable<Order>().DeleteOnSubmit(order);
                }
                context.GetTable<OrderRecord>().DeleteAllOnSubmit(records);
                context.SubmitChanges();
                ts.Complete();
            });
        }

        public void Update(int orderId, OrderUpdator updator) {
            Execute((context, ts) => {
                var transaction = LinqGetOrder(context, orderId);
                if (transaction == null) {
                    throw new Exception("Transaction can not be found.");
                }

                updator.Update(transaction);
                context.SubmitChanges();
                ts.Complete();
            });
        }

        public void UpdateStatus(int orderId, int status) {
            Execute((context, ts) => {
                var order = LinqGetOrder(context, orderId);
                if (order == null) {
                    throw new Exception("Transaction can not be found.");
                }

                if (order.Status != status) {
                    order.Status = status;
                    order.DateUpdated = DateTime.UtcNow;
                }

                context.SubmitChanges();
                ts.Complete();
            });
        }

        public List<OrderRecord> ListRecords() {
            return Execute(context => LinqGetAllOrderRecords(context).ToList());
        }

        public void InsertAllRecords(List<OrderRecord> records) {
            Execute(context => context.InsertAll(records));
        }

    }
}